from simpy import Environment

from util.dateutil import ts_str
from x_pc.core import System
from x_pc.core.carry.carry_101_ddj import DDJ
from x_pc.core.system_context import SysDataCtx
from x_pc.core.transport.transport_401_hc import HC
from x_pc.core.unit.unit_201_zbsx import ZBSX
from x_pc.core.unit.unit_202_zbcr import ZBCRSystem, ZBCR
from x_pc.core.unit.unit_210_iqccr import IQCCR, IQCCRSystem
from x_pc.exceptions import PcException
from x_pc.helpers import can_do_work
from x_pc.model.model import TaskCtx, Loc


class ZBLane(System):
    """
    栈板巷道
    """
    # 栈板系统
    system_zb = None
    # 堆垛机
    ddj_list: list[DDJ] = []
    # 栈板上下
    zbsx: ZBSX = None

    # 系统数据上下文
    sys_data: SysDataCtx = None

    def __init__(self, env: Environment, name: str, code: str, sys_data: SysDataCtx, system_zb, ddj_kwargs,
                 zbsx_kwargs):
        self.ddj_list: list[DDJ] = []
        #
        super().__init__(env, name, code)
        self.sys_data = sys_data
        self.system_zb = system_zb
        self.__init_ddj(env, ddj_kwargs)
        self.__init_zbsx(env, zbsx_kwargs)

    def __init_ddj(self, env: Environment, ddj_kwargs):
        """
        初始化 堆垛机
        :param env:
        :param ddj_kwargs:
        :return:
        """
        ddj = DDJ(env, '堆垛机', ddj_kwargs)
        if self.sys_data.is_valid_equ(ddj):
            self.ddj_list = [ddj]

    def __init_zbsx(self, env: Environment, zbsx_kwargs):
        """
        初始化 栈板上下

        :return:
        """
        zbsx = ZBSX(env, '栈板上下架口', zbsx_kwargs)
        if self.sys_data.is_valid_equ(zbsx):
            self.zbsx = zbsx

    def choose_task(self) -> TaskCtx:
        """
        选择任务
        :return:
        """
        return super().choose_task()

    def run_task(self, task_ctx: TaskCtx):
        """
        执行任务
        :param task_ctx:
        :return:
        """
        # 当 栈板上下口没有占用时 开始执行任务
        while self.running:
            # 栈板上下架不可用时
            if not self.zbsx:
                msg = f'[PC] ZB {self.name} ZBSX 栈板上下口 不可用'
                self.sys_data.logger.error(msg)
                break
            if self.zbsx.is_full():
                yield self.env.timeout(1)
            else:
                break
        # 当有可用堆垛机时 执行任务
        if not self.ddj_list:
            while self.running:
                yield self.env.timeout(1)
                if self.ddj_list:
                    break
        #
        ddj = self.ddj_list.pop(0)
        # 设备是否被锁定判断
        task = task_ctx.task
        process = task.process
        loc_distance = self.sys_data.get_lane_loc(process.begin_pos)
        if not loc_distance:
            msg = f'[PC] 栈板 堆垛机 耗时不存在，task: {task.task_id}, loc: {process.begin_pos}'
            self.sys_data.logger.error(msg)
            loc_cost = 999999
        else:
            loc_cost = loc_distance.time
        loc_cost = float(loc_cost) / self.sys_data.time_cost_ratio * 2
        pending_list = self.sys_data.get_pending_device(ddj.device_type(), ddj.device_code())
        if not can_do_work(self.env.now, loc_cost, pending_list):
            while self.running:
                yield self.env.timeout(1)
                if can_do_work(self.env.now, loc_cost, pending_list):
                    break
        msg = f'[PC] DDJ {self.name}, task_id: {task_ctx.task.task_id}, time: {ts_str(self.env.now)}'
        self.sys_data.logger.info(msg)
        with ddj.resource.request() as req:
            yield req
            process.begin_ts = self.env.now
            process.begin_pos = process.begin_pos.raw_loc if isinstance(process.begin_pos, Loc) else process.begin_pos
            process.set_origin_pack_type(task.origin_pack_type)
            process.set_origin_sku_num(task.origin_sku_num)
            process.operate_pack_type = process.ext_work_type.operate_pack_type
            process.operate_pack_num = 1
            #
            yield self.env.timeout(loc_cost)
            #
            process.end_ts = self.env.now
            process.end_pos = ddj.device_code()
            process.device_type = ddj.device_type()
            process.device_code = ddj.device_code()
        self.ddj_list.append(ddj)
        # 位置
        task_ctx.task.pre_process(self.sys_data.logger).end_pos = self.zbsx.loc()
        # 将任务 扔到具体的 栈板出入口
        process = task_ctx.task.process
        process.begin_ts = self.env.now
        process.begin_pos = self.zbsx.loc()
        process.set_origin_pack_type(task.pre_process().target_pack_type)
        process.set_origin_sku_num(task.pre_process().target_sku_num)
        process.operate_pack_type = process.ext_work_type.operate_pack_type
        process.operate_pack_num = 1
        #
        process.device_type = self.zbsx.device_type()
        process.device_code = self.zbsx.device_code()
        yield self.zbsx.store.put(task_ctx)
        process.end_ts = self.env.now
        process.end_pos = self.zbsx.loc()
        # 同时将任务 放至 环穿 任务列表
        self.system_zb.hc.push_task(TaskCtx(task_ctx.task, self.zbsx))  # XXX: 这块将 栈板出入口 一块传递至环穿

    def __str__(self):
        return f'{self.__class__.__name__}-{self.code} (ddj={self.ddj_list}, zbsx={self.zbsx})'

    def __repr__(self):
        return self.__str__()


class SystemZB(System):
    """
    栈板系统
    """
    # 巷道列表/字典
    lane_list: list[ZBLane] = []
    lane_dict: dict[str, ZBLane] = {}
    # 环穿
    hc: HC = None
    # 栈板出入口列表
    zbcr: ZBCRSystem = None
    iqccr: IQCCRSystem = None
    sys_data: SysDataCtx = None

    def __init__(self, env: Environment, name: str, sys_data: SysDataCtx):
        self.lane_list: list[ZBLane] = []
        self.lane_dict: dict[str, ZBLane] = {}
        #
        self.sys_data = sys_data
        super().__init__(env, name)
        self.__init_land_list(env, sys_data)
        self.__init_hc()
        self.__init_zbcr()
        self.__init_iqccr()

    def __init_land_list(self, env: Environment, sys_data: SysDataCtx):
        """
        初始化 巷道 列表
        :param sys_data:
        :return:
        """
        # 获取栈板数据
        zb_lane_data = sys_data.get_zb_lane_data()
        for lane in zb_lane_data:
            ddj_kwargs = zb_lane_data[lane][str(DDJ.device_type())]
            zbsx_kwargs = zb_lane_data[lane][str(ZBSX.device_type())]
            if not ddj_kwargs or not zbsx_kwargs:
                errors = []
                if not ddj_kwargs:
                    errors.append(f'堆垛机 无数据')
                if not zbsx_kwargs:
                    errors.append(f'栈板上下口 无数据')
                self.sys_data.logger.warning('[PC] 巷道数据异常 %s', errors)
                continue
            pl = ZBLane(env, f'栈板巷道-{lane}', lane, sys_data, self, ddj_kwargs, zbsx_kwargs)
            self.lane_list.append(pl)
            self.lane_dict[lane] = pl

    def __init_hc(self):
        """
        初始化 环穿
        :return:
        """
        hc_data = self.sys_data.get_data(HC)
        if not hc_data:
            raise PcException('环穿 无数据')
        hc = HC(self.env, '环穿', self, hc_data[0], self.sys_data)
        #
        if self.sys_data.is_valid_equ(hc):
            self.hc = hc

    def __init_zbcr(self):
        """
        初始化 栈板出入口
        :return:
        """
        zbcr_list = self.sys_data.get_data(ZBCR)
        self.zbcr = ZBCRSystem(self.env, '栈板出口', zbcr_list, self.sys_data)

    def __init_iqccr(self):
        """
        初始化 出入货口设备组
        """
        iqccr_list = self.sys_data.get_data(IQCCR)
        self.iqccr = IQCCRSystem(self.env, '出入货口设备组', iqccr_list, self.sys_data)

    def choose_task(self) -> TaskCtx:
        """
        从任务列表中选择 任务
        :return:
        """
        return super().choose_task()

    def run_task(self, task_ctx: TaskCtx):
        """
        执行 选择出来的 任务
        :param task_ctx:
        :return:
        """
        # 将任务 按位置 分配给不同的 巷道
        pos = task_ctx.task.begin_pos
        lane_code = pos.lane
        if lane_code not in self.lane_dict:
            msg = f'栈板巷道编号异常 code:{lane_code}, map: {self.lane_dict.keys()}'
            self.logger.error(msg)
            return
        # 根据位置选择巷道
        lane: ZBLane = self.lane_dict[str(lane_code)]
        lane.push_task(task_ctx)
        # 必须是生成器才行
        msg = f'[PC] ZB task: {task_ctx.task.task_id}, lane: {lane.name}, time: {ts_str(self.env.now)}'
        self.sys_data.logger.info(msg)
        yield self.env.timeout(0)

    def start(self):
        # 巷道 启动
        for lane in self.lane_list:
            lane.start()
        # 环穿 启动
        self.hc.start()
        # 栈板出入 启动
        self.zbcr.start()
        # 出入货口设备组 启动
        self.iqccr.start()
        super().start()

    def stop(self):
        # 巷道 停止
        for lane in self.lane_list:
            lane.stop()
        # 环穿 停止
        self.hc.stop()
        # 栈板出入 停止
        self.zbcr.stop()
        # 出入货口设备组 启动
        self.iqccr.stop()
        super().stop()
